package sisicotren.model.simulator;

import java.util.LinkedList;
import java.util.List;

import sisicotren.util.Log;

enum EngineState {
	CREATED,
	RUNNING,
	PAUSED,
	CANCELED,
	FINISHED
}

public class Engine {

	public Engine(Double cliPerMin, Double chRate, Integer queues, Integer totalCh) {
		this.state = EngineState.CREATED;
		this.totalQueues = queues;
		this.totalChannels = totalCh;
		this.cliPerMin = cliPerMin;
		this.chRate = chRate;
		
		this.channels = new LinkedList<Channel>();
		for(int j=0; j<totalChannels; j++)
			this.channels.add(new Channel(chRate));
		
		// colas ilimitadas
		this.queues = new LinkedList<Queue<Client>>();
		for(int i=0; i<totalQueues; i++) 
			this.queues.add(new Queue<Client>());
	
		this.system = new System(this.queues, channels);
		this.inj = new ClientsInjector(cliPerMin, system);
	}
		
	public void run() {
		this.startOfSimulation = java.lang.System.currentTimeMillis();
		Thread injThread = new Thread(inj);
		injThread.start();
		this.state = EngineState.RUNNING;
		while(this.state != EngineState.CANCELED && 
			  this.state != EngineState.FINISHED)
			Thread.yield();
	}
		
	public void run(Long seconds) {
		this.startOfSimulation = java.lang.System.currentTimeMillis();
		Thread injThread = new Thread(inj);
		// tiempo de la simulacion pasado a milisegundos.
		long endTime = this.startOfSimulation + (seconds * 1000);
		injThread.start();
		
		this.state = EngineState.RUNNING;
		
		while(this.state == EngineState.RUNNING ) {
			if(java.lang.System.currentTimeMillis() < endTime)
				Thread.yield();
			else
				this.state = EngineState.FINISHED;
		}
	}
	
	public void pause() {
		this.state = EngineState.PAUSED;
		this.inj.pause();
		this.system.pauseDispatchers();
		if(startOfSimulation > 0) {
			timeOfSimulation += 
				(java.lang.System.currentTimeMillis() - startOfSimulation);
		}
	}
	
	public void resume() {
		this.inj.resume();
		this.system.resumeDispatchers();
		this.state = EngineState.RUNNING;
		if(this.startOfSimulation > 0)
			this.startOfSimulation = java.lang.System.currentTimeMillis();
	}
	
	public void cancel() {
		this.inj.stop();
		this.system.stopDispatchers();
		this.state = EngineState.CANCELED;
	}
	
	public void finish() {
		this.inj.stop();
		this.state = EngineState.FINISHED;
		timeOfSimulation += 
			(java.lang.System.currentTimeMillis() - startOfSimulation);
	}
	
	public void offChannels() {
		this.system.stopDispatchers();
	}
	
	public void waitForAllQueuesInStateEmpty() {
		// espero a que se vacien todas las colas.
		boolean clientsInQueue = true;
		while(clientsInQueue) {
			Thread.yield();
			int clients = 0;
			for(int i=0; i<system.getQueues().size(); i++) {
				clients += system.getQueues().get(i).size();
			}
			clientsInQueue = clients > 0 ? true : false;
		}
	}
	
	public void waitForAllChannelsInStateEmpty() {
		boolean clientsInChannels = true;
		while(clientsInChannels) {
			int totalBusyChannels = 0;
			for(int j=0; j<totalChannels; j++)
				if(channels.get(j).getCurrentState() == ChannelState.BUSY)
					totalBusyChannels++;
			clientsInChannels = totalBusyChannels > 0;
		}
	}
	
	public Integer getTotalServedClients() {
		int totalServedClients = 0;
		for(int j=0; j<totalChannels; j++)
			totalServedClients += channels.get(j).getServedClients().size();
		
		return totalServedClients;
	}
	
	public void waitToDispatchAllClients() {
		Log.print("inj : " + getInjectedClients() + " Serv : " + getTotalServedClients());
		while( ! getInjectedClients().equals(getTotalServedClients())) {
			Thread.yield();
		}
	}
	
	public Integer getInjectedClients() {
		return inj.getClientsInjected();
	}

	public List<Channel> getChannels() {
		return channels;
	}

	public List<Queue<Client>> getQueues() {
		return this.queues;
	}
	
	public ClientsInjector getInjector() {
		return inj;
	}
	
	public List<ClientChannelDispatcher> getChannelsDispatchers() {
		return system.getChannelsDispatchers();
	}
	
	public SimulationStats getSimulationStats() {
		SimulationStats simulationStats = new SimulationStats();
		simulationStats.setLambdaUsed(this.cliPerMin);
		simulationStats.setMuUsed(this.chRate);
		simulationStats.setTotalClients(getTotalServedClients());
		simulationStats.setTotalSimulationTime(this.timeOfSimulation * (long)(60.00/this.time.time()));
		simulationStats.setExecutionTime(this.timeOfSimulation);
		
		for(int i=0; i<this.queues.size(); i++)
			simulationStats.
				addMaxQueueSize(i, this.queues.get(i).getMaxElemensInQueue());
		
		for(int i=0; i<this.queues.size(); i++)
			simulationStats.
				addMaxTimeInQueue(i, 
						this.queues.get(i).getMaxTimeElementInQueue() * (60.00/(this.time.time())/60000.00));
				
		return simulationStats;
	}
	
	public void setTime(SimulationTimeOptions time) {
		this.time = time;
	}

	public SimulationTimeOptions getTime() {
		return time;
	}

	public void reSynchronizeTime() {
		this.pause();
		ClientsInjector.setTimeOptions(getTime());
		ClientChannelDispatcher.setTimeOptions(getTime());
		this.resume();
	}
	
	private ClientsInjector inj;
	private System system;
	private Integer totalQueues;
	private Integer totalChannels;
	private Double chRate; // capac. canal.
	private Double cliPerMin; // arribos.
	private List<Channel> channels;
	private List<Queue<Client>> queues;
	private EngineState state;
	private long timeOfSimulation = 0L;
	private long startOfSimulation = 0L;
	private SimulationTimeOptions time = SimulationTimeOptions.REAL_TIME;
}
